From 3c28a0f427f681d86445a001306546e831e839e4 Mon Sep 17 00:00:00 2001 From: "emellor@ewan" Date: Tue, 4 Oct 2005 02:21:28 +0100 Subject: [PATCH] Move XendDomainInfo.{create,recreate,parseConfig} to the top level of the domain. This allows us to refer to them using an import statement, rather than a from .. import. This is a step towards getting rid of the xroot hack. All other references to XendDomainInfo methods need to be doubly qualified (once for the module, once for the class). Remove XendDomainDict, replacing it with a simple dictionary, folding the get_by_name method into XendDomain. Replace XendDomain.refresh_lock with a domains_lock which goes around any reference to XendDomain.domains or anything that will create or destroy a domain. This serialises most accesses through XendDomain, ensuring that we will not return stale state when racing against the watches fired in separate threads. This should have fixed bugs #270 and #234. Added a number of domain_get_xyz methods. Those named xyz_nr are to allow components internal to XendDomain (XendDomainInfo, XendCheckpoint) to call back into XendDomain without triggering further calls to XendDomain.refresh. The other methods simply make it clear which fallback behaviour is expected. Replace XendDomainInfo.domain_exists with XendDomainInfo.domain_by_name; the internals of this method needed to change to match those changes above, and it has been a misnomer for some time. Signed-off-by: Ewan Mellor --- tools/python/xen/xend/XendDomain.py | 250 ++++++++------- tools/python/xen/xend/XendDomainInfo.py | 297 +++++++++--------- tools/python/xen/xend/server/DevController.py | 2 +- tools/python/xen/xend/server/SrvDomainDir.py | 2 +- 4 files changed, 289 insertions(+), 262 deletions(-) diff --git a/tools/python/xen/xend/XendDomain.py b/tools/python/xen/xend/XendDomain.py index 9e13e8735e..f7da646e40 100644 --- a/tools/python/xen/xend/XendDomain.py +++ b/tools/python/xen/xend/XendDomain.py @@ -22,14 +22,16 @@ Needs to be persistent for one uptime. """ import os +import string import threading import xen.lowlevel.xc +import XendDomainInfo + from xen.xend import sxp from xen.xend import XendRoot from xen.xend import XendCheckpoint -from xen.xend.XendDomainInfo import XendDomainInfo from xen.xend import EventServer from xen.xend.XendError import XendError from xen.xend.XendLogging import log @@ -45,21 +47,10 @@ __all__ = [ "XendDomain" ] PRIV_DOMAIN = 0 -class XendDomainDict(dict): - def get_by_name(self, name): - try: - return filter(lambda d: d.getName() == name, self.values())[0] - except IndexError, err: - return None - class XendDomain: """Index of all domains. Singleton. """ - """Dict of domain info indexed by domain id.""" - domains = None - - ## public: def __init__(self): @@ -68,19 +59,30 @@ class XendDomain: # to import XendDomain from XendDomainInfo causes unbounded recursion. # So we stuff the XendDomain instance (self) into xroot's components. xroot.add_component("xen.xend.XendDomain", self) - self.domains = XendDomainDict() - self.refresh_lock = threading.Condition() + self.domains = {} + self.domains_lock = threading.Condition() self.watchReleaseDomain() - self.refresh() - self.dom0_setup() + + self.domains_lock.acquire() + try: + self.refresh() + self.dom0_setup() + finally: + self.domains_lock.release() + def list(self): """Get list of domain objects. @return: domain objects """ - self.refresh() - return self.domains.values() + self.domains_lock.acquire() + try: + self.refresh() + return self.domains.values() + finally: + self.domains_lock.release() + def list_sorted(self): """Get list of domain objects, sorted by name. @@ -103,7 +105,12 @@ class XendDomain: ## private: def onReleaseDomain(self): - self.refresh() + self.domains_lock.acquire() + try: + self.refresh() + finally: + self.domains_lock.release() + def watchReleaseDomain(self): from xen.xend.xenstore.xswatch import xswatch @@ -133,16 +140,8 @@ class XendDomain: return dominfo - def recreate_domain(self, xeninfo): - """Refresh initial domain info from db.""" - - dominfo = XendDomainInfo.recreate(xeninfo) - self._add_domain(dominfo) - return dominfo - - def dom0_setup(self): - dom0 = self.domain_lookup(PRIV_DOMAIN) + dom0 = self.domains[PRIV_DOMAIN] dom0.dom0_enforce_vcpus() @@ -179,50 +178,33 @@ class XendDomain: def refresh(self): """Refresh domain list from Xen. """ - self.refresh_lock.acquire() - try: - doms = self.xen_domains() - for d in self.domains.values(): - info = doms.get(d.getDomid()) - if info: - d.update(info) - else: - self._delete_domain(d.getDomid()) - for d in doms: - if d not in self.domains and not doms[d]['dying']: - try: - self.recreate_domain(doms[d]) - except: - if d == PRIV_DOMAIN: - log.exception( - "Failed to recreate information for domain " - "%d. Doing nothing except crossing my " - "fingers.", d) - else: - log.exception( - "Failed to recreate information for domain " - "%d. Destroying it in the hope of " - "recovery.", d) - try: - xc.domain_destroy(dom = d) - except: - log.exception('Destruction of %d failed.', d) - finally: - self.refresh_lock.release() - - - def update_domain(self, id): - """Update information for a single domain. - - @param id: domain id - """ - dominfo = self.xen_domain(id) - if dominfo: - d = self.domains.get(id) - if d: - d.update(dominfo) - else: - self._delete_domain(id) + doms = self.xen_domains() + for d in self.domains.values(): + info = doms.get(d.getDomid()) + if info: + d.update(info) + else: + self._delete_domain(d.getDomid()) + for d in doms: + if d not in self.domains and not doms[d]['dying']: + try: + dominfo = XendDomainInfo.recreate(doms[d]) + self._add_domain(dominfo) + except: + if d == PRIV_DOMAIN: + log.exception( + "Failed to recreate information for domain " + "%d. Doing nothing except crossing my " + "fingers.", d) + else: + log.exception( + "Failed to recreate information for domain " + "%d. Destroying it in the hope of " + "recovery.", d) + try: + xc.domain_destroy(dom = d) + except: + log.exception('Destruction of %d failed.', d) ## public: @@ -233,9 +215,14 @@ class XendDomain: @param config: configuration @return: domain """ - dominfo = XendDomainInfo.create(config) - self._add_domain(dominfo) - return dominfo + self.domains_lock.acquire() + try: + dominfo = XendDomainInfo.create(config) + self._add_domain(dominfo) + return dominfo + finally: + self.domains_lock.release() + def domain_configure(self, config): """Configure an existing domain. @@ -260,33 +247,72 @@ class XendDomain: raise XendError("can't read guest state file %s: %s" % (src, ex[1])) - def domain_get(self, id): - """Get up-to-date info about a domain. - @param id: domain id - @return: domain object (or None) - """ - self.update_domain(id) - return self.domains.get(id) + def domain_lookup(self, id): + self.domains_lock.acquire() + try: + self.refresh() + return self.domains.get(id) + finally: + self.domains_lock.release() - def domain_lookup(self, id): - self.refresh() - return self.domains.get(id) + def domain_lookup_nr(self, id): + self.domains_lock.acquire() + try: + return self.domains.get(id) + finally: + self.domains_lock.release() - def domain_lookup_by_name(self, name): - dominfo = self.domains.get_by_name(name) - if not dominfo: - try: - id = int(name) - dominfo = self.domain_lookup(id) - except ValueError: - pass - return dominfo + + def domain_lookup_by_name_or_id(self, name): + self.domains_lock.acquire() + try: + self.refresh() + return self.domain_lookup_by_name_or_id_nr(name) + finally: + self.domains_lock.release() + + + def domain_lookup_by_name_or_id_nr(self, name): + self.domains_lock.acquire() + try: + dominfo = self.domain_lookup_by_name_nr(name) + + if dominfo: + return dominfo + else: + try: + return self.domains.get(int(name)) + except ValueError: + return None + finally: + self.domains_lock.release() + + + def domain_lookup_by_name_nr(self, name): + self.domains_lock.acquire() + try: + matching = filter(lambda d: d.getName() == name, + self.domains.values()) + n = len(matching) + if n == 1: + return matching[0] + elif n > 1: + raise XendError( + 'Name uniqueness has been violated for name %s' % name) + else: + return None + finally: + self.domains_lock.release() def privilegedDomain(self): - return self.domains[PRIV_DOMAIN] + self.domains_lock.acquire() + try: + return self.domains[PRIV_DOMAIN] + finally: + self.domains_lock.release() def domain_unpause(self, id): @@ -321,12 +347,13 @@ class XendDomain: @param reason: shutdown reason: poweroff, reboot, suspend, halt """ - self.callInfo(domid, XendDomainInfo.shutdown, reason) + self.callInfo(domid, XendDomainInfo.XendDomainInfo.shutdown, reason) def domain_sysrq(self, domid, key): """Send a SysRq to the specified domain.""" - return self.callInfo(domid, XendDomainInfo.send_sysrq, key) + return self.callInfo(domid, XendDomainInfo.XendDomainInfo.send_sysrq, + key) def domain_destroy(self, domid): @@ -449,32 +476,39 @@ class XendDomain: def domain_device_create(self, domid, devconfig): """Create a new device for the specified domain. """ - return self.callInfo(domid, XendDomainInfo.device_create, devconfig) + return self.callInfo(domid, + XendDomainInfo.XendDomainInfo.device_create, + devconfig) def domain_device_configure(self, domid, devconfig, devid): """Configure an existing device in the specified domain. @return: updated device configuration """ - return self.callInfo(domid, XendDomainInfo.device_configure, + return self.callInfo(domid, + XendDomainInfo.XendDomainInfo.device_configure, devconfig, devid) def domain_device_refresh(self, domid, devtype, devid): """Refresh a device.""" - return self.callInfo(domid, XendDomainInfo.device_refresh, devtype, - devid) + return self.callInfo(domid, + XendDomainInfo.XendDomainInfo.device_refresh, + devtype, devid) def domain_device_destroy(self, domid, devtype, devid): """Destroy a device.""" - return self.callInfo(domid, XendDomainInfo.destroyDevice, devtype, - devid) + return self.callInfo(domid, + XendDomainInfo.XendDomainInfo.destroyDevice, + devtype, devid) def domain_devtype_ls(self, domid, devtype): """Get list of device sxprs for the specified domain.""" - return self.callInfo(domid, XendDomainInfo.getDeviceSxprs, devtype) + return self.callInfo(domid, + XendDomainInfo.XendDomainInfo.getDeviceSxprs, + devtype) def domain_vif_limit_set(self, id, vif, credit, period): @@ -518,7 +552,8 @@ class XendDomain: @param mem: memory target (in MiB) """ - self.callInfo(domid, XendDomainInfo.setMemoryTarget, mem << 10) + self.callInfo(domid, XendDomainInfo.XendDomainInfo.setMemoryTarget, + mem << 10) def domain_vcpu_hotplug(self, domid, vcpu, state): @@ -527,12 +562,13 @@ class XendDomain: @param vcpu: target VCPU in domain @param state: which state VCPU will become """ - self.callInfo(domid, XendDomainInfo.vcpu_hotplug, vcpu, state) + self.callInfo(domid, XendDomainInfo.XendDomainInfo.vcpu_hotplug, vcpu, + state) def domain_dumpcore(self, domid): """Save a core dump for a crashed domain.""" - self.callInfo(domid, XendDomainInfo.dumpCore) + self.callInfo(domid, XendDomainInfo.XendDomainInfo.dumpCore) ## private: diff --git a/tools/python/xen/xend/XendDomainInfo.py b/tools/python/xen/xend/XendDomainInfo.py index 7a479aa698..4547088856 100644 --- a/tools/python/xen/xend/XendDomainInfo.py +++ b/tools/python/xen/xend/XendDomainInfo.py @@ -120,6 +120,68 @@ ROUNDTRIPPING_CONFIG_ENTRIES = [ ] +def create(config): + """Create a VM from a configuration. + + @param config configuration + @raise: VmError for invalid configuration + """ + + log.debug("XendDomainInfo.create(%s)", config) + + vm = XendDomainInfo(getUuid(), parseConfig(config)) + vm.construct() + vm.refreshShutdown() + return vm + + +def recreate(xeninfo): + """Create the VM object for an existing domain. The domain must not + be dying, as the paths in the store should already have been removed, + and asking us to recreate them causes problems.""" + + log.debug("XendDomainInfo.recreate(%s)", xeninfo) + + assert not xeninfo['dying'] + + domid = xeninfo['dom'] + try: + dompath = GetDomainPath(domid) + if not dompath: + raise XendError( + 'No domain path in store for existing domain %d' % domid) + vmpath = xstransact.Read(dompath, "vm") + if not vmpath: + raise XendError( + 'No vm path in store for existing domain %d' % domid) + uuid = xstransact.Read(vmpath, "uuid") + if not uuid: + raise XendError( + 'No vm/uuid path in store for existing domain %d' % domid) + + log.info("Recreating domain %d, UUID %s.", domid, uuid) + + vm = XendDomainInfo(uuid, xeninfo, domid, True) + + except Exception, exn: + log.warn(str(exn)) + + uuid = getUuid() + + log.info("Recreating domain %d with new UUID %s.", domid, uuid) + + vm = XendDomainInfo(uuid, xeninfo, domid, True) + vm.storeVmDetails() + vm.storeDomDetails() + + vm.create_channel() + if domid == 0: + vm.initStoreConnection() + + vm.refreshShutdown(xeninfo) + return vm + + def restore(config): """Create a domain and a VM object to do a restore. @@ -134,7 +196,7 @@ def restore(config): except TypeError, exn: raise VmError('Invalid ssidref in config: %s' % exn) - vm = XendDomainInfo(uuid, XendDomainInfo.parseConfig(config), + vm = XendDomainInfo(uuid, parseConfig(config), xc.domain_create(ssidref = ssidref)) vm.storeVmDetails() vm.configure() @@ -143,10 +205,87 @@ def restore(config): return vm -def domain_exists(name): +def parseConfig(config): + def get_cfg(name, conv = None): + val = sxp.child_value(config, name) + + if conv and not val is None: + try: + return conv(val) + except TypeError, exn: + raise VmError( + 'Invalid setting %s = %s in configuration: %s' % + (name, val, str(exn))) + else: + return val + + + log.debug("parseConfig: config is %s" % str(config)) + + result = {} + + for e in ROUNDTRIPPING_CONFIG_ENTRIES: + result[e[0]] = get_cfg(e[0], e[1]) + + result['memory'] = get_cfg('memory', int) + result['mem_kb'] = get_cfg('mem_kb', int) + result['maxmem'] = get_cfg('maxmem', int) + result['maxmem_kb'] = get_cfg('maxmem_kb', int) + result['cpu'] = get_cfg('cpu', int) + result['image'] = get_cfg('image') + + try: + if result['image']: + result['vcpus'] = int(sxp.child_value(result['image'], + 'vcpus', 1)) + else: + result['vcpus'] = 1 + except TypeError, exn: + raise VmError( + 'Invalid configuration setting: vcpus = %s: %s' % + (sxp.child_value(result['image'], 'vcpus', 1), str(exn))) + + result['backend'] = [] + for c in sxp.children(config, 'backend'): + result['backend'].append(sxp.name(sxp.child0(c))) + + result['device'] = [] + for d in sxp.children(config, 'device'): + c = sxp.child0(d) + result['device'].append((sxp.name(c), c)) + + # Configuration option "restart" is deprecated. Parse it, but + # let on_xyz override it if they are present. + restart = get_cfg('restart') + if restart: + def handle_restart(event, val): + if not event in result: + result[event] = val + + if restart == "onreboot": + handle_restart('on_poweroff', 'destroy') + handle_restart('on_reboot', 'restart') + handle_restart('on_crash', 'destroy') + elif restart == "always": + handle_restart('on_poweroff', 'restart') + handle_restart('on_reboot', 'restart') + handle_restart('on_crash', 'restart') + elif restart == "never": + handle_restart('on_poweroff', 'destroy') + handle_restart('on_reboot', 'destroy') + handle_restart('on_crash', 'destroy') + else: + log.warn("Ignoring malformed and deprecated config option " + "restart = %s", restart) + + log.debug("parseConfig: result is %s" % str(result)) + return result + + +def domain_by_name(name): # See comment in XendDomain constructor. xd = get_component('xen.xend.XendDomain') - return xd.domain_lookup_by_name(name) + return xd.domain_lookup_by_name_nr(name) def shutdown_reason(code): """Get a shutdown reason from a code. @@ -181,152 +320,6 @@ class XendDomainInfo: MINIMUM_RESTART_TIME = 20 - def create(cls, config): - """Create a VM from a configuration. - - @param config configuration - @raise: VmError for invalid configuration - """ - - log.debug("XendDomainInfo.create(%s)", config) - - vm = cls(getUuid(), cls.parseConfig(config)) - vm.construct() - vm.refreshShutdown() - return vm - - create = classmethod(create) - - - def recreate(cls, xeninfo): - """Create the VM object for an existing domain. The domain must not - be dying, as the paths in the store should already have been removed, - and asking us to recreate them causes problems.""" - - log.debug("XendDomainInfo.recreate(%s)", xeninfo) - - assert not xeninfo['dying'] - - domid = xeninfo['dom'] - try: - dompath = GetDomainPath(domid) - if not dompath: - raise XendError( - 'No domain path in store for existing domain %d' % domid) - vmpath = xstransact.Read(dompath, "vm") - if not vmpath: - raise XendError( - 'No vm path in store for existing domain %d' % domid) - uuid = xstransact.Read(vmpath, "uuid") - if not uuid: - raise XendError( - 'No vm/uuid path in store for existing domain %d' % domid) - - log.info("Recreating domain %d, UUID %s.", domid, uuid) - - vm = cls(uuid, xeninfo, domid, True) - - except Exception, exn: - log.warn(str(exn)) - - uuid = getUuid() - - log.info("Recreating domain %d with new UUID %s.", domid, uuid) - - vm = cls(uuid, xeninfo, domid, True) - vm.storeVmDetails() - vm.storeDomDetails() - - vm.create_channel() - if domid == 0: - vm.initStoreConnection() - - vm.refreshShutdown(xeninfo) - return vm - - recreate = classmethod(recreate) - - - def parseConfig(cls, config): - def get_cfg(name, conv = None): - val = sxp.child_value(config, name) - - if conv and not val is None: - try: - return conv(val) - except TypeError, exn: - raise VmError( - 'Invalid setting %s = %s in configuration: %s' % - (name, val, str(exn))) - else: - return val - - - log.debug("parseConfig: config is %s" % str(config)) - - result = {} - - for e in ROUNDTRIPPING_CONFIG_ENTRIES: - result[e[0]] = get_cfg(e[0], e[1]) - - result['memory'] = get_cfg('memory', int) - result['mem_kb'] = get_cfg('mem_kb', int) - result['maxmem'] = get_cfg('maxmem', int) - result['maxmem_kb'] = get_cfg('maxmem_kb', int) - result['cpu'] = get_cfg('cpu', int) - result['image'] = get_cfg('image') - - try: - if result['image']: - result['vcpus'] = int(sxp.child_value(result['image'], - 'vcpus', 1)) - else: - result['vcpus'] = 1 - except TypeError, exn: - raise VmError( - 'Invalid configuration setting: vcpus = %s: %s' % - (sxp.child_value(result['image'], 'vcpus', 1), str(exn))) - - result['backend'] = [] - for c in sxp.children(config, 'backend'): - result['backend'].append(sxp.name(sxp.child0(c))) - - result['device'] = [] - for d in sxp.children(config, 'device'): - c = sxp.child0(d) - result['device'].append((sxp.name(c), c)) - - # Configuration option "restart" is deprecated. Parse it, but - # let on_xyz override it if they are present. - restart = get_cfg('restart') - if restart: - def handle_restart(event, val): - if not event in result: - result[event] = val - - if restart == "onreboot": - handle_restart('on_poweroff', 'destroy') - handle_restart('on_reboot', 'restart') - handle_restart('on_crash', 'destroy') - elif restart == "always": - handle_restart('on_poweroff', 'restart') - handle_restart('on_reboot', 'restart') - handle_restart('on_crash', 'restart') - elif restart == "never": - handle_restart('on_poweroff', 'destroy') - handle_restart('on_reboot', 'destroy') - handle_restart('on_crash', 'destroy') - else: - log.warn("Ignoring malformed and deprecated config option " - "restart = %s", restart) - - log.debug("parseConfig: result is %s" % str(result)) - return result - - - parseConfig = classmethod(parseConfig) - - def __init__(self, uuid, info, domid = None, augment = False): self.uuid = uuid @@ -957,10 +950,8 @@ class XendDomainInfo: if c in '_-.:/+': continue if c in string.ascii_letters: continue raise VmError('invalid vm name') - dominfo = domain_exists(name) - # When creating or rebooting, a domain with my name should not exist. - # When restoring, a domain with my name will exist, but it should have - # my domain id. + + dominfo = domain_by_name(name) if not dominfo: return if dominfo.is_terminated(): diff --git a/tools/python/xen/xend/server/DevController.py b/tools/python/xen/xend/server/DevController.py index c9fc72ebaa..e93d4051cd 100644 --- a/tools/python/xen/xend/server/DevController.py +++ b/tools/python/xen/xend/server/DevController.py @@ -193,7 +193,7 @@ class DevController: backdom_name = sxp.child_value(config, 'backend') if backdom_name: - backdom = xd.domain_lookup_by_name(backdom_name) + backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name) else: backdom = xd.privilegedDomain() diff --git a/tools/python/xen/xend/server/SrvDomainDir.py b/tools/python/xen/xend/server/SrvDomainDir.py index 430eb592ea..0bbcd8e607 100644 --- a/tools/python/xen/xend/server/SrvDomainDir.py +++ b/tools/python/xen/xend/server/SrvDomainDir.py @@ -38,7 +38,7 @@ class SrvDomainDir(SrvDir): self.xd = XendDomain.instance() def domain(self, x): - dom = self.xd.domain_lookup_by_name(x) + dom = self.xd.domain_lookup_by_name_or_id(x) if not dom: raise XendError('No such domain ' + str(x)) return SrvDomain(dom) -- 2.30.2